#include "umlrelationalitem.h"

#include "uml/effects/itemdrageffect.h"

UmlRelationalItem::UmlRelationalItem(UmlProject *project) : UmlGraphicsItem(project)
{
    this->_itemTypeFlags |= UmlItem::UmlRelationalItemType;

    this->_buttonPressed = false;
    this->_buttonUnderMouse = 0;

    this->setAcceptDrops(true);
    this->setHandlesChildEvents(false);
    this->_acceptDrop = false;

    this->_portList[UmlRelation::NORTH] = QMap<UmlRelation*, RelationPort*>();
    this->_portList[UmlRelation::SOUTH] = QMap<UmlRelation*, RelationPort*>();
    this->_portList[UmlRelation::WEST] = QMap<UmlRelation*, RelationPort*>();
    this->_portList[UmlRelation::EAST] = QMap<UmlRelation*, RelationPort*>();
}

UmlRelationalItem::~UmlRelationalItem()
{
    this->removeAllRelations();

    while (!this->_buttons.isEmpty()) delete this->_buttons.takeFirst();
}

void UmlRelationalItem::appendButton(RelationButton *button)
{
    button->setVisible(false);
    button->setRelationalItem(this);
    button->setZValue(std::numeric_limits<int>::max());
    button->setParentItem(this);
    this->_buttons.append(button);
    this->addToGroup(button);
}

void UmlRelationalItem::removeButton(RelationButton *button)
{
    this->_buttons.removeOne(button);
    this->removeFromGroup(button);
}

void UmlRelationalItem::appendRelation(UmlRelation *relation, UmlRelation::RelativeItemPosition direction)
{
    if (!this->portForRelation(relation, direction)) {
        this->_portList[direction][relation] = new RelationPort();
        this->addToGroup(this->_portList[direction][relation]);
    }

    this->drawPorts(direction);

    emit this->itemPositionHasChanged();
}

void UmlRelationalItem::removeRelation(UmlRelation *relation, UmlRelation::RelativeItemPosition direction)
{
    if (this->_portList[direction].contains(relation)) {

        // port holen
        RelationPort *port = this->_portList[direction].value(relation);

        // port aus der gruppe entfernen
        this->removeFromGroup(port);

        // port aus der protliste entfernen
        this->_portList[direction].remove(relation);

        // falls es keine weitere relation gibt die den port benötigt wird er gelöscht
        if (this->_portList[direction].keys(port).isEmpty()) {
            delete port;
            qDebug() << this->name() << "Port deletet";
        }

        this->drawPorts(direction);
    }
}

void UmlRelationalItem::removeAllRelations()
{
    foreach(UmlRelation *relation, this->relations()) {
        /*if (relation->item1() == this) {
            this->removeRelation(relation, relation->item2Direction());
            relation->item2()->removeRelation(relation, relation->item1Direction());
        } else {
            this->removeRelation(relation, relation->item1Direction());
            relation->item1()->removeRelation(relation, relation->item2Direction());
        }

        delete relation;*/

        relation->remove();
    }
}

UmlRelation::RelativeItemPosition UmlRelationalItem::storedRelationDirection(UmlRelation *relation)
{
    if (this->_portList[UmlRelation::NORTH].contains(relation)) {
        return UmlRelation::NORTH;
    }

    else if (this->_portList[UmlRelation::SOUTH].contains(relation)) {
        return UmlRelation::SOUTH;
    }

    else if (this->_portList[UmlRelation::WEST].contains(relation)) {
        return UmlRelation::WEST;
    }

    return UmlRelation::EAST;
}

RelationPort* UmlRelationalItem::portForRelation(UmlRelation *relation, UmlRelation::RelativeItemPosition direction)
{
    return this->_portList[direction].value(relation);
}

void UmlRelationalItem::moveRelation(UmlRelation *relation, UmlRelation::RelativeItemPosition from, UmlRelation::RelativeItemPosition to)
{
    this->removeRelation(relation, from);
    this->appendRelation(relation, to);
}

QList<UmlRelation*> UmlRelationalItem::relations()
{
    QList<UmlRelation::RelativeItemPosition> directions = this->_portList.uniqueKeys();
    QList<UmlRelation*> relations;

    foreach (UmlRelation::RelativeItemPosition direction, directions) {
        relations.append(this->_portList.value(direction).uniqueKeys());
    }

    return relations;
}

QList<UmlRelation*> UmlRelationalItem::relations(UmlRelation::RelationTypes relationType)
{
    QList<UmlRelation*> list;

    foreach (UmlRelation *relation, this->relations()) {
        if (relation->relationIsOfType(relationType)) {
            list.append(relation);
        }
    }

    return list;
}

QPointF UmlRelationalItem::centerPos()
{
    return this->boundingRect().center();
}

qreal UmlRelationalItem::topScene()
{
    return this->scenePos().y();
}

qreal UmlRelationalItem::leftScene()
{
    return this->scenePos().x();
}

qreal UmlRelationalItem::bottomScene()
{
    return this->scenePos().y() + this->boundingRect().height();
}

qreal UmlRelationalItem::rightScene()
{
    return this->scenePos().x() + this->boundingRect().width();
}

qreal UmlRelationalItem::top()
{
    return this->_boundingRect.top();
}

qreal UmlRelationalItem::bottom()
{
    return this->_boundingRect.bottom();
}

qreal UmlRelationalItem::left()
{
    return this->_boundingRect.left();
}

qreal UmlRelationalItem::right()
{
    return this->_boundingRect.right();
}

UmlRelation::RelativeItemPosition UmlRelationalItem::positionRelativeTo(UmlRelationalItem *item)
{
    QPointF thisCenter = this->mapToScene(this->centerPos());
    QPointF otherCenter = this->mapToScene(item->centerPos());

    if (this->bottomScene() < item->topScene()) {
        return UmlRelation::NORTH;
    }
    else if (this->topScene() > item->bottomScene()) {
        return UmlRelation::SOUTH;
    }
    else {
        if (this->rightScene() < item->leftScene()) {
            return UmlRelation::WEST;
        }
        else {
            return UmlRelation::EAST;
        }
    }
}

void UmlRelationalItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    if (this->_project->hasDraggedRelation()) {
        this->setGraphicsEffect(this->acceptDropEffect());
    }

    UmlGraphicsItem::hoverEnterEvent(event);
}

void UmlRelationalItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    UmlGraphicsItem::hoverLeaveEvent(event);
}

void UmlRelationalItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
    if (this->_buttonUnderMouse) {
        this->_buttonUnderMouse->setIsHovered(false);
    }

    this->_buttonUnderMouse = this->findButtonUnderMouse(event->pos());

    if (this->_buttonUnderMouse) {
        this->_buttonUnderMouse->setIsHovered(true);
    }

    if (this->_buttonPressed) {
        return;
    }

    UmlGraphicsItem::hoverMoveEvent(event);
}

void UmlRelationalItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (this->_buttonUnderMouse && event->button() == Qt::LeftButton) {
        this->_buttonPressed = true;
        this->_buttonUnderMouse->startRelationDrag();
    }

    UmlGraphicsItem::mousePressEvent(event);
}

void UmlRelationalItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if (this->_buttonUnderMouse && !this->findButtonUnderMouse(event->pos())) {
        this->_buttonUnderMouse->setIsHovered(false);
    }

    this->showButtons();

    this->_buttonPressed = false;

    UmlGraphicsItem::mouseReleaseEvent(event);
}

void UmlRelationalItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if (this->_buttonPressed) {
        return;
    }

    UmlGraphicsItem::mouseMoveEvent(event);
}

void UmlRelationalItem::showDropEffect(bool accept)
{
    if (accept == this->_acceptDrop) {
        return;
    }

    if (accept) {
        this->setGraphicsEffect(this->acceptDropEffect());
    }

    else {
        this->setGraphicsEffect(this->denyDropEffect());
    }

    this->_acceptDrop = accept;
}

QVariant UmlRelationalItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
    if (change == QGraphicsItem::ItemPositionHasChanged) {
        emit this->itemPositionHasChanged();
    }

    if (change == QGraphicsItem::ItemSceneHasChanged && this->scene()) {

        // add buttons
        foreach (RelationButton *button, this->_buttons) {
            button->setParentItem(this);
            button->setVisible(false);
            button->setParentItem(this);
            this->addToGroup(button);
        }

        // adding ports
        foreach (UmlRelation::RelativeItemPosition direction, this->_portList.keys()) {
            foreach (RelationPort *port, this->_portList.value(direction)) {
                port->setParentItem(this);
                this->addToGroup(port);
            }
        }

        // add relations
        foreach (UmlRelation *relation, this->relations()) {
            if (relation->item1() != this) {
                this->scene()->addItem(relation);
            }
        }

        this->drawPorts(UmlRelation::NORTH);
        this->drawPorts(UmlRelation::SOUTH);
        this->drawPorts(UmlRelation::EAST);
        this->drawPorts(UmlRelation::WEST);

        emit this->itemPositionHasChanged();

        this->updateButtonPos();
    }

    if (change == QGraphicsItem::ItemSelectedHasChanged) {
        if (!this->isSelected()) this->hideButtons();
    }

    return UmlGraphicsItem::itemChange(change, value);
}

void UmlRelationalItem::drawPorts(UmlRelation::RelativeItemPosition direction)
{
    QPoint pos;
    QList<RelationPort*> ports;

    foreach (RelationPort* port, this->_portList[direction].values()) {
        if (!ports.contains(port)) {
            ports.append(port);
        }
    }

    if (ports.isEmpty()) {
        return;
    }

    int count = ports.count() + 1;
    int space = 0;

    switch(direction) {
    case UmlRelation::SOUTH :
        space = this->_boundingRect.width() / count;
        foreach (RelationPort *port, ports) {
            port->setPos(pos);
            port->setItemBorderPoint(QPoint(space , this->_boundingRect.bottom()));
            port->setJoinPoint(port->itemBorderPoint() + QPoint(0, 30));
            port->setDirection(direction);
            space += space;
        }
        break;
    case UmlRelation::WEST :
        space = this->_boundingRect.height() / count;
        foreach (RelationPort *port, ports) {
            port->setPos(pos);
            port->setItemBorderPoint(QPoint(this->_boundingRect.left() , space));
            port->setJoinPoint(port->itemBorderPoint() + QPoint(-30, 0));
            port->setDirection(direction);
            space += space;
        }
        break;
    case UmlRelation::EAST :
        space = this->_boundingRect.height() / count;
        foreach (RelationPort *port, ports) {
            port->setPos(pos);
            port->setItemBorderPoint(QPoint(this->_boundingRect.right() , space));
            port->setJoinPoint(port->itemBorderPoint() + QPoint(30, 0));
            port->setDirection(direction);
            space += space;
        }
        break;
    default :
        space = this->_boundingRect.width() / count;
        foreach (RelationPort *port, ports) {
            port->setPos(pos);
            port->setItemBorderPoint(QPoint(space , this->_boundingRect.top()));
            port->setJoinPoint(port->itemBorderPoint() + QPoint(0, -30));
            port->setDirection(direction);
            space += space;
        }
    }
}

void UmlRelationalItem::addButtonsToGroup()
{
    foreach (RelationButton *button, this->_buttons) {
        button->setParentItem(this);
        this->addToGroup(button);
    }
}

void UmlRelationalItem::updateButtonPos()
{
    if (this->_buttons.isEmpty()) {
        return;
    }

    int buttonCount = this->_buttons.count();
    qreal itemWidth = this->boundingRect().width();

    qreal buttonsWidth  = 0;
    qreal buttonsHeight = 0;

    // calc button box
    foreach (RelationButton *button, this->_buttons) {
        buttonsWidth += button->width();
    }
    buttonsWidth += (buttonCount - 1) * 10;
    buttonsHeight = this->_buttons.at(0)->height();

    // calc button box position
    qreal sideSpacing = (itemWidth - buttonsWidth) / 2;

    // setting button positions
    QPointF pos = QPointF(sideSpacing, 0 - buttonsHeight - 10);
    foreach (RelationButton *button, this->_buttons) {
        button->setTopLeft(pos);
        pos.setX(pos.x() + button->boundingRect().width() + 10);
    }
}



void UmlRelationalItem::showButtons()
{
    foreach (RelationButton *button, this->_buttons) {
        button->setVisible(true);
    }
}

void UmlRelationalItem::hideButtons()
{
    foreach (RelationButton *button, this->_buttons) {
        button->setVisible(false);
    }
}

RelationButton* UmlRelationalItem::findButtonUnderMouse(QPointF pos)
{
    foreach(RelationButton *button, this->_buttons) {
        if (button->mapRectToParent(button->boundingRect()).contains(pos)) {
            return button;
        }
    }

    return 0;
}

UmlRelationalItem* UmlRelationalItem::boundingItem()
{
    return this;
}
